package com.kamistoat.meimeistore.commonsecurity.aspect;

import com.kamistoat.meimeistore.commonsecurity.auth.AuthUtil;
import com.kamistoat.meimeistore.commonsecurity.annotation.RequiresLogin;
import com.kamistoat.meimeistore.commonsecurity.annotation.RequiresPermissions;
import com.kamistoat.meimeistore.commonsecurity.annotation.RequiresRoles;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 对被 @RequiresLogin @RequiresPermissions @RequiresRoles 注解的方法的调用进行认证。使用AOP技术。
 * 理解思路和 InnerAuthAspect 中完全相同。
 */
@Aspect
@Component
public class PreAuthorizeAspect {

    @Autowired
    AuthUtil authUtil;

    /**
     * 空构造函数，有没有没区别
     */
    public PreAuthorizeAspect() {
    }

    /**
     * 定义AOP签名 (切入所有使用鉴权注解的方法)
     */
    public static final String POINTCUT_SIGN = " @annotation(com.kamistoat.meimeistore.commonsecurity.annotation.RequiresLogin) || "
            + "@annotation(com.kamistoat.meimeistore.commonsecurity.annotation.RequiresPermissions) || "
            + "@annotation(com.kamistoat.meimeistore.commonsecurity.annotation.RequiresRoles)";

    /**
     * 声明AOP签名。作用是简化代码
     */
    @Pointcut(POINTCUT_SIGN)
    public void pointcut() {
    }

    /**
     * 当切点的条件是 || 时说明不确定切点处到底满足哪个条件。这时最好不要将切点条件作为输入参数写到环绕方法中。
     * 最好还是在拦截下来之后，单独进行判断
     */
    // @Around(POINTCUT_SIGN)  // 直接使用拼接的拦截条件也是可以的
    @Around("pointcut()")  // 依然是环绕切面
    public Object preAuthAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 获取切点处的方法签名。查看 MethodSignature 类，发现其只有两个属性 -- 返回值类型 和 方法本身Method
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        // 对切点处的方法进行鉴权
        checkMethodAuthAnnotation(methodSignature.getMethod());
        // 只有上面的鉴权通过，才真正执行方法体中的内容，否则到不了下面
        try {
            Object proceedResult = proceedingJoinPoint.proceed();
            return proceedResult;
        } catch (Throwable e) {
            // 这里的异常是在捕获 执行方法时出现的异常，不是权限鉴定时的异常
            e.printStackTrace();
            throw e;
        }
    }

    /**
     * 检查一个 Method上是否具有 @RequiresLogin @RequiresPermissions @RequiresRoles 等鉴权注解。并执行对应的鉴权。
     * 注意下面所有的鉴权并不是从数据库中鉴权，而是从 ThreadLocal.LoginUser 进行鉴权.
     * 而有关 ThreadLocal.LoginUser 的填充并不是在这里进行，而是在 Gateway模块进行的。
     *
     * @param method
     */
    public void checkMethodAuthAnnotation(Method method) {
        // 如果含有 @RequiresLogin
        RequiresLogin requiresLogin = method.getAnnotation(RequiresLogin.class);
        if (requiresLogin != null) {
            authUtil.checkLogin();
        }
        // 如果含有 @RequiresRoles
        RequiresRoles requiresRoles = method.getAnnotation(RequiresRoles.class);
        if (requiresRoles != null) {
            authUtil.checkRole(requiresRoles);
        }
        // 如果含有 @RequiresPermissions
        RequiresPermissions requiresPermissions = method.getAnnotation(RequiresPermissions.class);
        if (requiresPermissions != null) {
            authUtil.checkPermi(requiresPermissions);
        }
    }
}




























